package io.summer.redis.config;

import io.summer.redis.tool.RedissonTool;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonSpringCacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 默认使用RedissonSpringCacheManager
 *
 * @author Moonlight
 */
@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {
  @Autowired
  private RedisConnectionFactory factory;

  @Autowired
  private ApplicationContext ctx;

  @Autowired
  private RedissonTool redissonTool;

  @Value("${spring.redis.redisson.cache-config:classpath:cache-config.yml}")
  private String cacheConfigFile;

  @Value("${spring.redis.cache-ttl:3600}")
  private Long ttl;

  @Override
  @Bean
  public KeyGenerator keyGenerator() {
    return (o, method, args) -> {
      StringBuilder sb = new StringBuilder();
      sb.append(o.getClass().getName()).append(":");
      sb.append(method.getName()).append(":");
      for (Object obj : args) {
        sb.append(obj.toString());
      }
      return sb.toString();
    };
  }

  @Override
  @Bean
  public CacheErrorHandler errorHandler() {
    return new SimpleCacheErrorHandler();
  }

  @Override
  @Bean
  public CacheResolver cacheResolver() {
    return new SimpleCacheResolver(Objects.requireNonNull(redissonSpringCacheManager()));
  }

  public CacheManager redissonSpringCacheManager() {
    RedissonSpringCacheManager cacheManager = new RedissonSpringCacheManager(redissonTool.client());
    Map<String, ? extends CacheConfig> config = getCacheConfig();
    cacheManager.setConfig(config);
    return cacheManager;
  }

  public CacheManager redisCacheManager() {
    RedisCacheConfiguration cacheConfiguration =
      RedisCacheConfiguration.defaultCacheConfig()
        .disableCachingNullValues()
        .entryTtl(Duration.ofSeconds(ttl))
        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisConfig.getRedisSerializer()));
    Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>(4);
    getCacheConfig().forEach((k, c) -> {
      cacheConfigurations.put(k, cacheConfiguration.entryTtl(Duration.ofMillis(c.getTTL())));
    });
    return RedisCacheManager.builder(factory)
      .cacheDefaults(cacheConfiguration)
      .withInitialCacheConfigurations(cacheConfigurations).build();
  }

  private Map<String, ? extends CacheConfig> getCacheConfig() {
    Map<String, ? extends CacheConfig> config;
    try {
      Resource resource = ctx.getResource(cacheConfigFile);
      InputStream is = resource.getInputStream();
      config = CacheConfig.fromYAML(is);
    } catch (IOException e) {
      throw new IllegalArgumentException("Can't parse config", e);
    }
    return config;
  }
}
